﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Services;
using ADONETDataServicesExtension.Providers;
using ADONETDataServicesExtension.Utils;
using System.Linq.Expressions;
using System.Reflection;

namespace ADONETDataServicesExtension
{
    public class DataServiceConfiguration : FrameworkEncapsulatedType<IDataServiceConfiguration>, IDataServiceConfiguration
    {
        internal const string FrameworkTypeName = "System.Data.Services.DataServiceConfiguration";

        public DataServiceConfiguration(IDataServiceConfiguration dataServiceConfiguration)
            : base(dataServiceConfiguration)
        {
        }
        public DataServiceConfiguration(IDataServiceProvider provider)
        {
            FrameworkObject = ReflectionHelper.CreateInstance<IDataServiceConfiguration>(FrameworkTypeName, provider.FrameworkObject);
        }

        public int MaxBatchCount
        {
            get
            {
                return FrameworkObject.MaxBatchCount;
            }
            set
            {
                FrameworkObject.MaxBatchCount = value;
            }
        }

        public int MaxChangesetCount
        {
            get
            {
                return FrameworkObject.MaxChangesetCount;
            }
            set
            {
                FrameworkObject.MaxChangesetCount = value;
            }
        }

        public int MaxExpandCount
        {
            get
            {
                return FrameworkObject.MaxExpandCount;
            }
            set
            {
                FrameworkObject.MaxExpandCount = value;
            }
        }

        public int MaxExpandDepth
        {
            get
            {
                return FrameworkObject.MaxExpandDepth;
            }
            set
            {
                FrameworkObject.MaxExpandDepth = value;
            }
        }

        public int MaxObjectCountOnInsert
        {
            get
            {
                return FrameworkObject.MaxObjectCountOnInsert;
            }
            set
            {
                FrameworkObject.MaxObjectCountOnInsert = value;
            }
        }

        public int MaxResultsPerCollection
        {
            get
            {
                return FrameworkObject.MaxResultsPerCollection;
            }
            set
            {
                FrameworkObject.MaxResultsPerCollection = value;
            }
        }

        public void RegisterKnownType(Type type)
        {
            FrameworkObject.RegisterKnownType(type);
        }

        public void SetEntitySetAccessRule(string name, EntitySetRights rights)
        {
            FrameworkObject.SetEntitySetAccessRule(name, rights);
        }

        public void SetServiceOperationAccessRule(string name, ServiceOperationRights rights)
        {
            FrameworkObject.SetServiceOperationAccessRule(name, rights);
        }

        public bool UseVerboseErrors
        {
            get
            {
                return FrameworkObject.UseVerboseErrors;
            }
            set
            {
                FrameworkObject.UseVerboseErrors = value;
            }
        }

        public void CheckResourceRightsForRead(ResourceContainer resourceContainer, bool singleResult)
        {
            ReflectionHelper.InvokeMethod(FrameworkTypeName, "CheckResourceRightsForRead", FrameworkObject, resourceContainer.FrameworkObject, singleResult);
        }

        public static Expression ComposeQueryInterceptors(IDataService service, ResourceContainer resourceContainer)
        {
            return ReflectionHelper.InvokeStaticMethod<Expression>(FrameworkTypeName, "ComposeQueryInterceptors", service.FrameworkObject, resourceContainer.FrameworkObject);
        }

        public void InvokeStaticInitialization(Type type)
        {
            ReflectionHelper.InvokeVoidMethod(FrameworkTypeName, "InvokeStaticInitialization", FrameworkObject, type);
        }
        
        public void RegisterCallbacks(Type type)
        {
            BindingFlags bindingAttr = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
            while (type != null)
            {
                foreach (MethodInfo info in type.GetMethods(bindingAttr))
                {
                    QueryInterceptorAttribute[] customAttributes = (QueryInterceptorAttribute[])info.GetCustomAttributes(typeof(QueryInterceptorAttribute), true);
                    foreach (QueryInterceptorAttribute attribute in customAttributes)
                    {
                        ResourceContainer container = Provider.TryResolveContainerName(attribute.EntitySetName);
                        if (container == null)
                            throw new InvalidOperationException(Strings.DataService_AttributeEntitySetNotFound(attribute.EntitySetName, info.Name, type.FullName));
                        CheckQueryInterceptorSignature(type, info, container);
                        if (!info.IsAbstract)
                            container.AddReadAuthorizationMethod(info);
                    }
                    ChangeInterceptorAttribute[] attributeArray2 = (ChangeInterceptorAttribute[])info.GetCustomAttributes(typeof(ChangeInterceptorAttribute), true);
                    foreach (ChangeInterceptorAttribute attribute2 in attributeArray2)
                    {
                        ResourceContainer container2 = Provider.TryResolveContainerName(attribute2.EntitySetName);
                        if (container2 == null)
                            throw new InvalidOperationException(Strings.DataService_AttributeEntitySetNotFound(attribute2.EntitySetName, info.Name, type.FullName));
                        ParameterInfo[] parameters = info.GetParameters();
                        if (parameters.Length != 2)
                            throw new InvalidOperationException(Strings.DataService_ChangeInterceptorIncorrectParameterCount(info.Name, type.FullName, parameters.Length));
                        CheckParameterIsNotOut(info, parameters[0]);
                        CheckParameterIsNotOut(info, parameters[1]);
                        Type parameterType = parameters[0].ParameterType;
                        if (!parameterType.IsAssignableFrom(container2.ElementType))
                            throw new InvalidOperationException(Strings.DataService_AuthorizationParameterNotAssignable(parameters[0].Name, info.Name, type.FullName, parameterType.FullName, container2.ElementType));
                        if (parameters[1].ParameterType != typeof(UpdateOperations))
                            throw new InvalidOperationException(Strings.DataService_AuthorizationParameterNotResourceAction(parameters[1].Name, info.Name, type.FullName, typeof(UpdateOperations).FullName));
                        Type returnType = info.ReturnType;
                        if (returnType != typeof(void))
                            throw new InvalidOperationException(Strings.DataService_AuthorizationMethodNotVoid(info.Name, type.FullName, returnType.FullName));
                        if (!info.IsAbstract)
                            container2.AddWriteAuthorizationMethod(info);
                    }
                }
                type = type.BaseType;
            }
        }

        private static void CheckParameterIsNotOut(MethodInfo method, ParameterInfo parameter)
        {
            ReflectionHelper.InvokeStaticVoidMethod(FrameworkTypeName, "CheckParameterIsNotOut", method, parameter);
        }

        private static void CheckQueryInterceptorSignature(Type type, MethodInfo method, ResourceContainer container)
        {
            ReflectionHelper.InvokeStaticVoidMethod(FrameworkTypeName, "CheckQueryInterceptorSignature", type, method, container.FrameworkObject);
        }

        private IDataServiceProvider Provider
        {
            get
            {
                return ReflectionHelper.Convert(ReflectionHelper.GetField(FrameworkTypeName, "provider", FrameworkObject), frameworkValue => new IDataServiceProvider(frameworkValue));
            }
            set
            {
                ReflectionHelper.SetField(FrameworkTypeName, "provider", FrameworkObject, value == null ? null : value.FrameworkObject);
            }
        }

        public void ApplyToProvider(object dataSourceInstance)
        {
            ReflectionHelper.InvokeVoidMethod(FrameworkTypeName, "ApplyToProvider", FrameworkObject, dataSourceInstance);
        }

        public void CheckResourceRights(ResourceContainer container, EntitySetRights requiredRights)
        {
            ReflectionHelper.InvokeVoidMethod(FrameworkTypeName, "CheckResourceRights", FrameworkObject, container.FrameworkObject, requiredRights);
        }
    }
}
